////////////////////////////////////////////////////////////////////////////////////////////////////

#include "CAxisControl.h"
#include "CUserInterface.h"
#include "CLevelEditor.h"
#include "DoUndoCommands.h"
#include "CEventReceiver.h"
#include "CMoveCommand.h"
#include "CMultiCommand.h"
#include "CScaleCommand.h"
#include "ISceneCollisionManager.h"
#include "ISceneManager.h"
#include "ISceneNode.h"
#include "ICameraSceneNode.h"
#include "SViewFrustum.h"
#include "IrrlichtDevice.h"
#include "CSceneObject.h"
////////////////////////////////////////////////////////////////////////////////////////////////////

extern CGlobals* g;
extern CUserInterface* ui;
extern CLevelEditor* editor;

////////////////////////////////////////////////////////////////////////////////////////////////////

void drawThick2DLine (position2di p1, position2di p2, int thickness, SColor color);
void drawThick2DLine (vector2df p1, vector2df p2, int thickness, SColor color);
bool project3DTo2D (vector3df pt, vector2df* result, ICameraSceneNode* camera = 0);
void drawThick2DArrow (vector2df p1, vector2df p2, int thickness, SColor color);

////////////////////////////////////////////////////////////////////////////////////////////////////

CAxisControl::CAxisControl (array<ISceneNode*> nodes, vector3df axis, SColor normalColor, SColor highlightColor) 
{
	m_type = E_AXIS;
	m_nodes = nodes;
	m_axis = axis;
	m_normalColor = normalColor;
	m_highlightColor = highlightColor;
	m_activated = false;
	m_highlighted = false;
	m_camera = 0;
	m_centroid = vector3df();
	for (u32 i = 0; i < m_nodes.size(); ++i)
	{
		m_centroid += m_nodes[i]->getAbsolutePosition();
	}
	m_centroid /= (f32)m_nodes.size();
}

////////////////////////////////////////////////////////////////////////////////////////////////////

CAxisControl::~CAxisControl ()
{
}

////////////////////////////////////////////////////////////////////////////////////////////////////

float pointToSegmentDistance (vector2df pt, vector2df s1, vector2df s2)
{
	vector2df r = pt - s1, v = s2 - s1;
	float lineParam = v.dotProduct(r) / v.dotProduct(v);
	if (0.0f <= lineParam && lineParam <= 1.0f)
		return (r - lineParam * v).getLength();
	else if (lineParam < 0.0f)
		return (pt - s1).getLength();
	else
		return (pt - s2).getLength();
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CAxisControl::OnEvent (const SEvent& event)
{
	switch (event.EventType)
	{
	case EET_MOUSE_INPUT_EVENT:
		return onMouseEvent(event.MouseInput);
	}
	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

vector2df CAxisControl::getScreenAxis () const
{
	array<vector2df> coords;
	getScreenCoordinates(&coords);

	assert(coords.size() == 2);
	
	return coords[1] - coords[0];
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CAxisControl::isPointOverControl (position2di pt) const
{
	array<vector2df> coords;
	getScreenCoordinates(&coords);

	if (coords.size() == 2)
	{
		return pointToSegmentDistance(vector2df(f32(pt.X), f32(pt.Y)), coords[0], coords[1]) <= 4.0f;
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

// returns the two points which are closest on the two lines o1+t*d1 and o2+t*d2
void getClosestPoints (vector3df o1, vector3df d1, vector3df o2, vector3df d2, vector3df* p1, vector3df* p2)
{
	vector3df n = d1.crossProduct(d2);
	vector3df n1 = d1.crossProduct(n);
	vector3df n2 = d2.crossProduct(n);
	n.normalize();
	n1.normalize();
	n2.normalize();

	plane3df pl1(o1, n1);
	plane3df pl2(o2, n2);

	if (p1) pl2.getIntersectionWithLine(o1, d1, *p1);
	if (p2) pl1.getIntersectionWithLine(o2, d2, *p2);
}

////////////////////////////////////////////////////////////////////////////////////////////////////

ICameraSceneNode* cloneActiveCamera ()
{
	ICameraSceneNode* oldCam = g->sceneManager->getActiveCamera();
	ICameraSceneNode* result = g->sceneManager->addCameraSceneNode();
	result->grab();
	g->sceneManager->setActiveCamera(oldCam);
	result->setTarget(oldCam->getTarget());
	result->setPosition(oldCam->getPosition());
	result->setUpVector(oldCam->getUpVector());
	result->setAspectRatio(oldCam->getAspectRatio());
	result->setFOV(oldCam->getFOV());
	result->setProjectionMatrix(oldCam->getProjectionMatrix(), oldCam->isOrthogonal());
	result->updateAbsolutePosition();
	return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CAxisControl::onMouseEvent (const SEvent::SMouseInput& event)
{
	position2di mouse = g->device->getCursorControl()->getPosition();//(event.X, event.Y);

	if (editor->isPointOverGUI(mouse))
		return false;

	bool mouseOverControl = isPointOverControl(mouse);

	m_highlighted = mouseOverControl && !g->eventReceiver->IsKeyDown(KEY_CONTROL);
	
	int result = ui->getEditAction();

	switch (event.Event)
	{
	case EMIE_LMOUSE_PRESSED_DOWN:
		if (mouseOverControl && !g->eventReceiver->IsKeyDown(KEY_CONTROL))
		{
			array<vector2df> coords;
			getScreenCoordinates(&coords);
			mouse.X = (s32)coords[0].X;
			mouse.Y = (s32)coords[0].Y;
			g->device->getCursorControl()->setPosition(mouse);

			m_activated = true;
			g->eventReceiver->GrabAllEvents(this);

			if (result == UI_EDIT_ACTION_MOVE ) 
			{
				m_nodeStartPos.set_used(m_nodes.size());
				for (u32 i = 0; i < m_nodes.size(); ++i)
					m_nodeStartPos[i] = m_nodes[i]->getPosition();
			}
			else if (result == UI_EDIT_ACTION_SCALE)
			{
				m_nodeStartScale.set_used(m_nodes.size());
				for (u32 i = 0; i < m_nodes.size(); ++i)
					m_nodeStartScale[i] = m_nodes[i]->getScale();
			}

			m_mouseStart = mouse;
			m_camera = cloneActiveCamera();
			return true;
		}
		break;

	case EMIE_LMOUSE_LEFT_UP:
		if (m_activated)
		{
			m_activated = false;
			g->eventReceiver->ReleaseGrab(this);
			m_camera->remove();
			m_camera = 0;
			switch ( result )
			{
				case UI_EDIT_ACTION_MOVE :
				{
					if (g->eventReceiver->IsKeyDown(KEY_SHIFT))
					{
						for (u32 i = 0; i < m_nodes.size(); ++i)
						{
							//if (!isUserDefinedMesh(m_nodes[i]))
							//{
								CSceneObject* pNode = editor->cloneNode(m_nodes[i]);
								ISceneNode* node = pNode->m_SceneNode;								

								node->setPosition(m_nodes[i]->getPosition());
								m_nodes[i]->setPosition(m_nodeStartPos[i]);

								//editor->AddInsertCommand(pNode);
								
							//}
							//else
							//{
							//	m_nodes[i]->setPosition(m_nodeStartPos[i]);
							//}
						}

					}
					else
					{
						if (m_nodes.size() > 1)
						{
							CMultiCommand* moves = new CMultiCommand;
							moves->setDescription(stringw(L"move ") + stringw(m_nodes.size()) + " objects");
							for (u32 i = 0; i < m_nodes.size(); ++i)
							{
								CMoveCommand* move = new CMoveCommand(m_nodes[i]->getID(), m_nodeStartPos[i], m_nodes[i]->getPosition());
								moves->addCommand(move);
								move->drop();
							}
							editor->AddCommand(moves);

						}
						else
						{	
							editor->AddCommand(new CMoveCommand(m_nodes[0]->getID(), m_nodeStartPos[0], m_nodes[0]->getPosition()));
													
						}
					}
				}
				break;
				case UI_EDIT_ACTION_SCALE :
				{
					if (m_nodes.size() > 1)
					{
						CMultiCommand* scales = new CMultiCommand;
						scales->setDescription(stringw(L"scale ") + stringw(m_nodes.size()) + " objects");
						for (u32 i = 0; i < m_nodes.size(); ++i)
						{
							CScaleCommand* scale = new CScaleCommand(m_nodes[i]->getID(), m_nodeStartScale[i], m_nodes[i]->getScale());
							scales->addCommand(scale);
							scale->drop();
						}
						editor->AddCommand(scales);
					}
					else
					{
						vector3df newScale = m_nodes[0]->getScale();
						editor->AddCommand(new CScaleCommand(m_nodes[0]->getID(), m_nodeStartScale[0], newScale));
					}
				}
				break;
			}
			return true;
		}
		break;

	case EMIE_MOUSE_MOVED:
		if (m_activated)
		{
			ISceneCollisionManager* colmgr = g->sceneManager->getSceneCollisionManager();

			vector3df mouseStart, moveTo;
			line3df rayStart = colmgr->getRayFromScreenCoordinates(m_mouseStart, m_camera);
			getClosestPoints(m_centroid, m_axis, rayStart.start, rayStart.getVector(), &mouseStart, 0);

			line3df rayMoveTo = colmgr->getRayFromScreenCoordinates(mouse, m_camera);
			getClosestPoints(m_centroid, m_axis, rayMoveTo.start, rayMoveTo.getVector(), &moveTo, 0);
			switch ( result )
			{
				case UI_EDIT_ACTION_MOVE :
				{
					for (u32 i = 0; i < m_nodes.size(); ++i)
					{
						vector3df move = moveTo - mouseStart;
						matrix4 matAbsTransform = m_nodes[i]->getParent()->getAbsoluteTransformation();
						matAbsTransform.rotateVect(move);
						m_nodes[i]->setPosition(m_nodeStartPos[i] + move );
					}
				}
				break;

				case UI_EDIT_ACTION_SCALE :
				{
					for (u32 i = 0; i < m_nodes.size(); ++i)
					{
						vector3df scale = moveTo - mouseStart;
						matrix4 matAbsTransform = m_nodes[i]->getParent()->getAbsoluteTransformation();
						matAbsTransform.rotateVect(scale);
						m_nodes[i]->setScale(m_nodeStartScale[i] + scale/10 );
					}
				}
				break;

			}
			ui->updateProperties();
		}
		break;
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CAxisControl::draw ()
{
	SColor color = (m_highlighted || m_activated) ? m_highlightColor : m_normalColor;

	if (m_activated)
	{
		int result = ui->getEditAction();
		if (result == UI_EDIT_ACTION_MOVE ) 
		{
			vector2df start, end;
			project3DTo2D(m_centroid, &start, m_camera);

			vector3df move = m_nodes[0]->getPosition() - m_nodeStartPos[0];
			matrix4 matAbsTransform = m_nodes[0]->getParent()->getAbsoluteTransformation();
			matAbsTransform.rotateVect(move);
			project3DTo2D(m_centroid + move, &end, m_camera);
			drawThick2DArrow(start, end, 2, color);
		}
		else if (result == UI_EDIT_ACTION_SCALE)
		{
			vector2df start, end;
			project3DTo2D(m_centroid, &start, m_camera);

			vector3df scale = m_nodes[0]->getScale() - m_nodeStartScale[0];
			matrix4 matAbsTransform = m_nodes[0]->getParent()->getAbsoluteTransformation();
			matAbsTransform.rotateVect(scale);
			project3DTo2D(m_centroid + scale * 10.0f, &end, m_camera);
			drawThick2DArrow(start, end, 2, color);
		}
	}
	else
	{
		array<vector2df> coords;
		getScreenCoordinates(&coords);
		if (coords.size() == 2)
		{
			//void drawThick2DArrow (vector2df p1, vector2df p2, int thickness, SColor color);
			drawThick2DArrow(coords[0], coords[1], 2, color);
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CAxisControl::getScreenCoordinates (array<vector2df>* coords) const
{
	vector3df nodePos = m_centroid;
	ICameraSceneNode* cam = g->sceneManager->getActiveCamera();
	plane3df pl = cam->getViewFrustum()->planes[SViewFrustum::VF_NEAR_PLANE];

	float dist = -pl.getDistanceTo(nodePos);

	float sign = (plane3df(nodePos, m_axis).getDistanceTo(cam->getPosition()) >= 0) ? 1.0f : -1.0f;

	float scale = 1;
	if (dist > 1.0f)
	{
		scale = dist/5.0f;
	}

	vector3df p1 = nodePos;
	vector3df p2 = p1 + sign*scale*m_axis;

	vector2df proj;
	if (project3DTo2D(p1, &proj, m_camera)) coords->push_back(proj);
	if (project3DTo2D(p2, &proj, m_camera)) coords->push_back(proj);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
